2024-08-19T00:23:40,900200090+00:00
On a cloud vps, access to manage the instance is provided via
network. It is essential to make sure that access to the server is
always possible, without the risk of being locked out because of network
misconfiguration. For example, the first ufw
rule to add
before enabling the firewall is ufw allow ssh
so that ssh
service is accessible after enabling the firewall. There is also
netplan --debug try
to test netplan
configuration before making permanent changes.
One of the high risk network configuration is setting up a VPN tunnel
on a cloud server. The chance of misconfiguration is higher when
specifying a tunnel as route for accessing the internet, i.e. setting up
a VPN as gateway. A misconfiguration can cause the vps instance to be
locked from the outside, which needs some form of physical
access via serial console or ipmi interface to recover from network
misconfiguration.
A route is created when specifying AllowedIPs
in the
wireguard configuration. By default, the route is added to the
main
route table, so all traffic except to the wireguard
gateway peer will be routed through wireguard interface, which is not
always the desired outcome when activating wireguard tunnel on a cloud
vps instance.
To override this behavior, there is a Table
field which
can be used to specify a routing table associated with a specific
wireguard interface. With Table
field configured, the usual
traffic is not modified so one will not lose access to the cloud vps
instance, especially when specifying a catch-all ip range as
AllowedIPs
, i.e. 0.0.0.0/0
for ipv4 and
::/0
for ipv6.
Here is an example of /etc/wireguard/wireguard0.conf
with Table
field configured.
[Interface]
PrivateKey = hidden
Address = 10.0.0.2/32
DNS = 10.0.0.1
Table = 51820
[Peer]
PublicKey = redacted
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
On the configuration above, the Table
field is
configured with value of 51820
. So, the usual traffic will
follow the default gateway on the main
routing table. If
VPN use is desired, a policy routing has to be configured.
In the above configuration, the wireguard0
interface
will only route traffic through the tunnel if the packet flows through
51820
routing table. To redirect the flow of the packet, a
specific rule based on firewall mark or based on owner can be
enforced.
If a user with uid
of 1001
wants to use the
vpn route for the traffic owned by that user, a rule can be specified
easily using ip rule
tool.
# Activate the vpn tunnel
systemctl start wg-quick@wireguard0.service
# Route traffic owned by user with uid of 1001 via the vpn
ip rule add uidrange 1001-1001 table 51820
# Since the vpn doesn't provide ipv6, disable routing of ipv6 packets for uid 1001
ip -6 rule add uidrange 1001-1001 unreachable
# Otherwise, add the same rule for ipv6
# ip -6 rule add uidrange 1001-1001 table 51820
Other methods of policy routing, such as marking packet using
iptables
via fwmark
can also be used. The
setup involves firewall (iptables
or nft
) in
addition to ip-rule
. Read the documentation of the specific
tools such as iptables
and ip-rule
for more
in-depth understanding about policy based routing (i.e. splitting
traffic via several interfaces).
A vps is configured as seedbox to a private tracker, but the vps
provider prohibits the use of vps for this specific purpose. So a
wireguard based VPN is set up to route transmission
traffic
through wireguard0
interface.
# On debian, the transmission-daemon package comes with a user configured for the installed service: debian-transmission
$ id debian-transmission
uid=111(debian-transmission) gid=116(debian-transmission) groups=116(debian-transmission)
$ ip route show table 51820
default dev wireguard0 scope link
# First, a rule as kill switch to prevent traffic leaking via the main route if the tunnel is down
$ sudo ip rule add uidrange 111-111 unreachable
# enforce packet owned by uid 111 to be routed via table 51820
$ sudo ip rule add uidrange 111-111 lookup 51820
$ sudo ip -6 rule add uidrange 111-111 unreachable
# Allow traffic via ipv6 if the tunnel supports ipv6 (optional)
# $ sudo ip -6 rule add uidrange 111-111 lookup 51820
This setup can be automated during tunnel bring-up via
PostUp
field in the
/etc/wireguard/wireguard0.conf
file.
[Interface]
PrivateKey = hidden
# If the tunnel provider supports IPv6, there will be IPv6 address specified here, such as ffdc:17ba:8192::0dbf/128 or 2001:db8:f4c3::1337/128
Address = 10.0.0.2/32
DNS = 10.0.0.1
Table = 51820
PostUp = ip rule add uidrange 111-111 unreachable
PostUp = ip rule add uidrange 111-111 lookup 51820
PostUp = ip -6 rule add uidrange 111-111 unreachable
# Uncomment if IPv6 will be used via the tunnel (if the tunnel provider supports it)
# PostUp = ip -6 rule add uidrange 111-111 lookup 51820
PreDown = ip rule del uidrange 111-111 unreachable
PreDown = ip rule del uidrange 111-111 lookup 51820
PreDown = ip -6 rule del uidrange 111-111 unreachable
# Uncomment if IPv6 routing was enabled before (look at previous comment)
# PreDown = ip rule del uidrange 111-111 lookup 51820
[Peer]
PublicKey = redacted
# If IPv6 will be used, add ::/0 to the AllowedIPs
AllowedIPs = 0.0.0.0/0
Endpoint = vpn.example.com:51820
To verify that the setup is working, check the public ip address returned when using the specific uid. Verify that the returned address is indeed the vpn public ip address instead of the address of the cloud vps instance.
sudo -u debian-transmission curl https://icanhazip.com
The entire transmission-daemon
service can be modified
to require wg-quick@wireguard0.service
to make this fully
automated.
sudo systemctl edit transmission-daemon.service
Add this snippet to the text editor provided by systemctl edit
command. Put the snippet inside the space between ###
as
instructed.
[Unit]
Requires=wg-quick@wireguard0.service
[Service]
ExecStartPre=curl https://icanhazip.com
With the above setups, only packets owned by uid 111 will be routed
through wireguard0
interface. Other packets will use the
main
routing table.
The above setup is my practical use of split tunneling, inspired by Android which containerized each app in its own uid.